// Copyright (C) 2013 Michael Leviston (michael.leviston@gmail.com)

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#ifndef ECS_SYSTEMBASE_H
#define ECS_SYSTEMBASE_H

#include "System.h"

namespace ECS
{

	/// \brief  Base class for all systems.
	///
	///			Provides common functionality for all system types.
	///			Each user defined system must override the MakeNode(), Update() and DeleteSpecificNode() functions inherited from System.
	///			Each system has a master list of all generic nodes, and system specific lists of its own node types 
	///			e.g. move system has move nodes, graphics system may have sprite nodes, primitive nodes, particle nodes etc...
	///
	///			Each system stores all component ID's associated with it.
	///			As we defer entity/component deletion till the end of the update loop, specific nodes can have naked pointers directly to component members
	/// \author Michael Leviston
	template <class T>
	class SystemBase : public System 
	{
	public:

		/// Constructor takes update priority of the system (order of update in world->update() )
		SystemBase(unsigned int Priority) : nID(1), priority(Priority) {}

		/// Get the priority of the system
		unsigned int GetPriority() { return priority; }

		/// Get the Type of the system
		unsigned int GetType()     { return T::type; }

		/// Get the list of generic nodes managed by the system
		std::map<unsigned int, spNode>* GetNodes() { return &nodes; }

		/// Type of the system determined by CRTP
		static unsigned int type;

		/// Delete all nodes with the selected component ID
		void DeleteGenericNode(unsigned int ComponentID)
		{	
			// Iterate through all generic nodes
			auto itB = nodes.begin();
			auto itE = nodes.end();
		
			for(itB; itB != itE;)
			{
				if(itB->second->HasComponentID(ComponentID) )
				{
					// We are going to delete this node
					// So we must remove all of the registered components from this system too
					unsigned int nID = itB->second->GetID();

					auto IDs = itB->second->GetComponentIDs();
					auto itBC = IDs.begin();
					auto itEC = IDs.end();

					// Iterate through all of the cIDs registered with the node/system
					for(itBC; itBC != itEC; ++itBC)
					{
						componentIDs.erase(*itBC);	// Deregister the component from the system
					}

					itB = nodes.erase(itB);	 // Delete generic node from system master list

					DeleteSpecificNode(nID); // Delete specific node from sub-system lists
				}
				else
				{
					++itB;
				}
			}
		}
	
	protected:

		/// Add a generic node with two components
		unsigned int AddGenericNode(wpComponent wpC1, wpComponent wpC2)
		{
			spNode spN(new Node(GetNodeID() ) );
			unsigned int id1 = spN->AddComponent( wpC1 );
			unsigned int id2 = spN->AddComponent( wpC2 );
			nodes[spN->GetID()] = spN;
			componentIDs.insert(id1);
			componentIDs.insert(id2);

			return spN->GetID();
		}

		/// Add a generic node with three components
		unsigned int AddGenericNode(wpComponent wpC1, wpComponent wpC2, wpComponent wpC3)
		{
			spNode spN(new Node(GetNodeID() ) );
			unsigned int id1 = spN->AddComponent( wpC1 );
			unsigned int id2 = spN->AddComponent( wpC2 );
			unsigned int id3 = spN->AddComponent( wpC3 );
			nodes[spN->GetID()] = spN;
			componentIDs.insert(id1);
			componentIDs.insert(id2);
			componentIDs.insert(id3);

			return spN->GetID();
		}

		/// Check if this system has any nodes with the component ID
		bool HasComponentID(unsigned int ID) 
		{
			auto itF = componentIDs.find(ID);
			if(itF != componentIDs.end() )
			{
				return true;
			}
			else
				return false;	
		}

		std::map<unsigned int, spNode> nodes; ///< Map of nodes contained by this system
		std::set<unsigned int> componentIDs;  ///< Set of component IDs associated with generic nodes

	private:

		/// Get a new Node ID
		unsigned int GetNodeID()   { return nID++; }

		unsigned int nID;		///< Current node ID
		unsigned int priority;  ///< Update priority of the system
	};

	static unsigned int nextSType = 0;
	template <typename T> unsigned int SystemBase<T>::type(nextSType++);

}

#endif // ECS_SYSTEMBASE_H